SpringSecurity底层原理和认证授权流程总结

您所在的位置:网站首页 spring security 认证 SpringSecurity底层原理和认证授权流程总结

SpringSecurity底层原理和认证授权流程总结

2023-08-12 07:31| 来源: 网络整理| 查看: 265

安全框架(springsecurity、shiro等)主要分为两个部分:

认证:系统认为用户是否能登录授权:系统判断用户是否有权限去做某些事情 项目主要使用了:基于token的用户权限认证与授权

 模块一:登录时springsecurity获取用户的信息,比如用户名、密码、和查数据库得到其权限列表 如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式 进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限 值,并以用户名为 key ,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息 生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带 到 header 请求头中, Spring-security 解析 header 头获取 token 信息,解析 token 获取当前 用户名,根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前 请求是否有权限访问  项目后台系统权限管理模块说明

用户管理:表示可以登录这个后台网站的用户

 角色管理:不同的角色可以访问不同的权限菜单集合,要给每个用户分配一种角色

 权限管理数据库模型:

菜单表acc_permission

一共有两级菜单,一级菜单的pid为1,二级菜单的pid为一级菜单的id   菜单角色关系表acl_role_permission 维护菜单和角色的关系

用户角色关系表acl_user_role

维护用户和角色之间的关系 

JWT工具类  项目中用户登录成功后,需要根据用户名生成token,放在cookie中,jwt就是生成token的工具。当前端给服务器发送请求时,请求头会携带token信息,服务器通过校验token,来确定该用户的权限token就是一种服务器颁发的访问令牌,服务器会设置签名算法和密钥,通过签名算法和密钥以及token的有效载荷,生成token的签名,保证数据不会被篡改。

 

 

 签名哈希

注意在权限管理模块使用的JWT工具类是:但是这个工具类比较简单,我就拿common_utils中的JWT工具类来说明!!!!!!

public class JwtUtils { public static final long EXPIRE = 1000 * 60 * 60 * 24;//设置token的过期时间 public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//秘钥加密 //生成token字符串的方法(根据用户的id和昵称生成字符串) public static String getJwtToken(String id, String nickname){ String JwtToken = Jwts.builder() .setHeaderParam("typ", "JWT")//设置令牌类型为JWT令牌 .setHeaderParam("alg", "HS256")//设置签名算法为HS256 .setSubject("guli-user") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置token的过期时间 //claim是token的主体部分 .claim("id", id) .claim("nickname", nickname) .signWith(SignatureAlgorithm.HS256, APP_SECRET) //设置签名算法和密钥 .compact(); return JwtToken; } /** * 判断token是否存在与有效 * @param jwtToken * @return */ public static boolean checkToken(String jwtToken) { if(StringUtils.isEmpty(jwtToken)) return false; try { Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 判断token是否存在与有效 * @param request * @return */ public static boolean checkToken(HttpServletRequest request) { try { String jwtToken = request.getHeader("token");//把token放在request的头信息中 if(StringUtils.isEmpty(jwtToken)) return false; Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 根据token获取会员id * @param request * @return */ //解析token,得到用户id public static String getMemberIdByJwtToken(HttpServletRequest request) { String jwtToken = request.getHeader("token");//从请求头中获取token字符串 if(StringUtils.isEmpty(jwtToken)) return ""; Jws claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); Claims claims = claimsJws.getBody(); return (String)claims.get("id"); } }

springsecurity4个工具类(这一部分具体直接看代码注释)

认证、授权过滤器(具体看代码注释)

认证过滤器   认证的过程是,先从登录请求中拿到对应的用户名和密码,对用户名密码进行登录校验(与数据库中比对),认证成功后执行successfulAuthentication方法,通过JWT工具类根据用户名生成token放在客户端的cookie中然后在redis中加入一个key,value,分别是用户名和权限列表认证失败则在响应中返回错误信息。

授权过滤器

        当一个用户已经登录后,下次再访问服务器时,请求头中包含token信息,

        服务器先从header中获取token,解析得到用户名,从redis中获取权限列表,由springsecurity给当前用户赋予权限*(哪些可以访问,哪些不能访问)

 使用redisTemplate操作redis service_acl模块 UserDetailsServiceImpl类 因为在springsecurity包的核心配置类中要使用userDetailsService接口的实现类来操作数据库,查询用户的用户名密码等数据

因此 service_acl模块中要有这个实现类——UserDetailsServiceImpl类 @Service("userDetailsService")//该bean对象的引用名就是userDetailsService public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Autowired private PermissionService permissionService; /*** * 根据账号获取用户信息 * @param username: * @return: org.springframework.security.core.userdetails.UserDetails */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库中取出用户信息 User user = userService.selectByUsername(username); // 判断用户是否存在 if (null == user){ //throw new UsernameNotFoundException("用户名不存在!"); } // 返回UserDetails实现类 com.atguigu.serurity.entity.User curUser = new com.atguigu.serurity.entity.User(); BeanUtils.copyProperties(user,curUser); //查询用户的权限列表 List authorities = permissionService.selectPermissionValueByUserId(user.getId()); SecurityUser securityUser = new SecurityUser(curUser); securityUser.setPermissionValueList(authorities); return securityUser; } }

 service_acl其他部分: 其他部分主要是对这五张表进行增删改查操作

api_gateway模块:  CorsConfig:解决跨域问题 @Configuration public class CorsConfig { //解决跨域问题,有这个类后,其他模块的controller就不用加@CrossOrigin注解了 @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }

 具体跨域的问题,看下面三个博客

面试官:你们项目中是怎么解决跨域的?

SpringSecurity解决跨域问题

springboot解决跨域和集成springsecurity解决跨域

 application.properties: # 服务端口 server.port=8222 # 服务名 spring.application.name=service-gateway # nacos服务地址 spring.cloud.nacos.discovery.server-addr=localhost:8848 #下面都是gateway配置 #使用服务发现路由(开启nacos服务发现) spring.cloud.gateway.discovery.locator.enabled=true #设置路由id spring.cloud.gateway.routes[0].id=service-acl #设置路由的uri lb://nacos注册服务名称 spring.cloud.gateway.routes[0].uri=lb://service-acl #设置路由断言,代理servicerId为auth-service的/auth/路径 spring.cloud.gateway.routes[0].predicates= Path=/*/acl/** #配置service-edu服务 spring.cloud.gateway.routes[1].id=service-edu spring.cloud.gateway.routes[1].uri=lb://service-edu spring.cloud.gateway.routes[1].predicates= Path=/eduservice/** #配置service-ucenter服务 spring.cloud.gateway.routes[2].id=service-ucenter spring.cloud.gateway.routes[2].uri=lb://service-ucenter spring.cloud.gateway.routes[2].predicates= Path=/ucenterservice/** #配置service-ucenter服务 spring.cloud.gateway.routes[3].id=service-cms spring.cloud.gateway.routes[3].uri=lb://service-cms spring.cloud.gateway.routes[3].predicates= Path=/cmsservice/** spring.cloud.gateway.routes[4].id=service-msm spring.cloud.gateway.routes[4].uri=lb://service-msm spring.cloud.gateway.routes[4].predicates= Path=/edumsm/** spring.cloud.gateway.routes[5].id=service-order spring.cloud.gateway.routes[5].uri=lb://service-order spring.cloud.gateway.routes[5].predicates= Path=/orderservice/** spring.cloud.gateway.routes[6].id=service-order spring.cloud.gateway.routes[6].uri=lb://service-order spring.cloud.gateway.routes[6].predicates= Path=/orderservice/** spring.cloud.gateway.routes[7].id=service-oss spring.cloud.gateway.routes[7].uri=lb://service-oss spring.cloud.gateway.routes[7].predicates= Path=/eduoss/** spring.cloud.gateway.routes[8].id=service-statistic spring.cloud.gateway.routes[8].uri=lb://service-statistic spring.cloud.gateway.routes[8].predicates= Path=/staservice/** spring.cloud.gateway.routes[9].id=service-vod spring.cloud.gateway.routes[9].uri=lb://service-vod spring.cloud.gateway.routes[9].predicates= Path=/eduvod/** spring.cloud.gateway.routes[10].id=service-edu spring.cloud.gateway.routes[10].uri=lb://service-edu spring.cloud.gateway.routes[10].predicates= Path=/eduservice/** gateway、nacos和权限管理模块的关系 

包含acl的请求路径会被网关拦截,然后去注册中心找对应的服务名,再转发到对应的权限管理模块中去

 从上面配置文件看出,网关服务(api_gateway)对外暴露的端口是8222,也就是说前端的请求url是网关的ip+8222

网关接收到请求后,网关根据请求url的字符信息,去注册中心发现对应的服务(服务发现)。

前提是各个服务已经在nacos注册中心注册了

具体的nacos服务发现的规则(也就是怎么根据请求url找到对应的nacos注册中心的服务名)由上面的配置文件规定

和nginx的配置文件中配置的是一样的意思

springboot本质上是一个过滤器链,也就是说提供很多过滤器,这里只详细介绍项目中用到的过滤器。

UsernamePasswordAuthenticationFilter(登录过滤器) 项目中TokenLoginFilter继承了这个类

作用:对/login登录的post请求做拦截,校验表单中的用户名和密码  /** *

* 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验 *

* * @author qy * @since 2019-11-08 */ public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; private TokenManager tokenManager; private RedisTemplate redisTemplate; public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) { this.authenticationManager = authenticationManager; this.tokenManager = tokenManager; this.redisTemplate = redisTemplate; this.setPostOnly(false); this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { try { User user = new ObjectMapper().readValue(req.getInputStream(), User.class); return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList())); } catch (IOException e) { throw new RuntimeException(e); } } /** * 登录成功 * @param req * @param res * @param chain * @param auth * @throws IOException * @throws ServletException */ @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { SecurityUser user = (SecurityUser) auth.getPrincipal(); String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername()); redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList()); ResponseUtil.out(res, R.ok().data("token", token)); } /** * 登录失败 * @param request * @param response * @param e * @throws IOException * @throws ServletException */ @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { ResponseUtil.out(response, R.error()); } }  打开UsernamePasswordAuthenticationFilter源码,有下面这个方法 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //1、先判断是不是post提交 if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { //2、从请求request获取用户名和密码 String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); //3、查数据库,对用户名和密码进行校验 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } }  先判断是不是post提交从请求request获取用户名和密码查数据库,对用户名和密码进行校验 过滤器是如何被加载的?  springboot帮我们完成了springsecurity的自动化配置 两个重要的接口: UserDetails 项目中SecurityUser实现了这个接口

 SecurityUser /** *

* 安全认证用户详情信息 *

* * @author qy * @since 2019-11-08 */ @Data @Slf4j public class SecurityUser implements UserDetails { //当前登录用户 private transient User currentUserInfo; //当前权限 private List permissionValueList; public SecurityUser() { } public SecurityUser(User user) { if (user != null) { this.currentUserInfo = user; } } // 表示获取登录用户所有权限 @Override public Collection


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3